﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Apollo;
using NHibernate.Mapping;
using System.Reflection;
using Apollo.Core;
using NHibernate;

namespace Apollo.Website
{
    public partial class Edit : System.Web.UI.Page
    {
        protected object obj = null;
        protected Entity entity = null;
        protected EntityEdit entityEdit = null;
        protected IList<Controls_Edit_FormGroup> formGroups = new List<Controls_Edit_FormGroup>();

        public bool EnableSave { get; set; }
        public bool EnableDelete { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            this.Response.Cache.SetCacheability(HttpCacheability.NoCache);

            try
            {

                // clear messages
                LiteralJS.Text = "";
                LiteralTabsHeaders.Text = "";

                try
                {
                    EditActionSubscription subscription = Apollo.Application.Current.PrepareEditAction();

                    if (subscription == null)
                    {
                        MessageList.AddMessage(new ActionMessage("No valid subscriber for this action!", ActionMessageType.System));
                        return;
                    }
                    else
                    {
                        this.entityEdit = subscription.Edit;
                        this.entity = subscription.Entity;
                    }

                }
                catch (Exception ex) {
                    MessageList.AddMessage(new ActionMessage(ex.Message, ActionMessageType.System));
                }

                if (entity == null)
                {
                    MessageList.AddMessage(new ActionMessage("Invalid entity definition!", ActionMessageType.System));
                    return;
                }

                if (string.IsNullOrEmpty(Request["ObjectId"]) || Request["ObjectId"] == "-1" || Request["ObjectId"] == "0")
                {
                    LabelEntityName.Text = "Create new";
                }
                else
                {
                    Projection idProjection = new Projection(entity.IdentifierProperty, entity.MappedClass);

                    object idValue = Apollo.Application.Deserialize(Request["ObjectId"], idProjection.PropertyType);

                    obj = this.Get(entity, idValue);

                    Projection displayProjection = new Projection(entity.DisplayProperty, entity.MappedClass);

                    object displayObj = displayProjection.Apply(obj);

                    if (displayProjection.IsRelation && displayObj != null)
                    {

                        Entity innerEntity = Apollo.Application.Meta<Entity>()
                            .SingleOrDefault<Entity>(en => en.Name == displayProjection.ClassType.Name);
                        displayProjection = new Projection(innerEntity.DisplayProperty, innerEntity.MappedClass);

                        displayObj = displayProjection.Apply(displayObj);

                    }

                    if (displayObj == null)
                        LabelEntityName.Text = "{" + idValue.ToString() + "}";
                    else {
                        string format = displayProjection.EntityProperty.FormatString;

                        if (string.IsNullOrEmpty(format))
                            LabelEntityName.Text = Apollo.Application.Serialize(displayObj, true);
                        else
                            LabelEntityName.Text = Apollo.Application.Serialize(displayObj, format);

                    }
                }


                // set entity icon & entity name
                ImageEntityIcon.ImageUrl = string.IsNullOrEmpty(entity.IconPath) ? "/css/icons/icon_01.png" : entity.IconPath;

                this.LoadEntity(entity, entityEdit ?? entity.DefaultEdit, obj);
            }
            catch (Exception ex)
            {
                MessageList.AddMessage(new ActionMessage(ex.Message, ActionMessageType.System));
            }
        }


        protected void DoAction(string actionCode, object obj)
        {
            ActionContext context = Apollo.Application.Current.DoAction(actionCode, obj);

            foreach (ActionMessage msg in context.Messages)
                this.MessageList.AddMessage(msg);
        }

        protected ActionContext Save()
        {
            bool isNewObject = false;

            if (obj == null) // create new object
            {
                isNewObject = true;
                obj = Activator.CreateInstance(entity.MappedClass);
            }

            ActionContext context = new ActionContext();

            foreach (Controls_Edit_FormGroup group in formGroups)
            {
                foreach (ActionMessage msg in group.Save(obj).Messages)
                    context.AddMessage(msg);
            }

            if (context.State == ActionState.Failed)
            {
                foreach (ActionMessage msg in context.Messages)
                    this.MessageList.AddMessage(msg);

                return context;
            }

            ITrackable objTrackble = obj as ITrackable;
            if (objTrackble != null)
            {
                if (isNewObject)
                {
                    objTrackble.InsertBy = this.User.Identity.Name;
                    objTrackble.InsertDate = DateTime.Now;
                }
                else
                {
                    objTrackble.ModifiedBy = this.User.Identity.Name;
                    objTrackble.ModifiedDate = DateTime.Now;
                }
            }

            if (isNewObject && obj.GetType().GetInterface(typeof(IHistory<>).Name) != null) {
                obj.GetType().GetProperty("LearnDate").SetValue(obj, DateTime.Now, null);
            }

            context = Apollo.Application.Current.Save(obj);

            foreach (ActionMessage msg in context.Messages)
                this.MessageList.AddMessage(msg);

            if (context.State == ActionState.Succesful && isNewObject)
            {
                LinkButtonSave.Visible = false;
                LinkButtonSaveNew.Visible = false;
                LinkButtonSaveClose.Visible = false;
                LinkButtonDelete.Visible = false;
            }

            return context;
        }

        protected void ButtonSave_Click(object sender, EventArgs e)
        {
            this.Save();
        }

        protected void ButtonDelete_Click(object sender, EventArgs e)
        {
            if (obj == null)
            {
                this.MessageList.AddMessage(new ActionMessage("Could not delete new object!", ActionMessageType.System));
                return;
            }

            ActionContext context = Apollo.Application.Current.Delete(obj);

            foreach (ActionMessage msg in context.Messages)
                this.MessageList.AddMessage(msg);

            if (context.State == ActionState.Succesful)
            {
                ActionSave.Visible = false;
                ActionSaveNew.Visible = false;
                ActionSaveClose.Visible = false;
                ActionDelete.Visible = false;
            }
        }

        protected void ButtonSaveNew_Click(object sender, EventArgs e)
        {
            if (this.Save().State == ActionState.Succesful)
            {
                string qsOther = "";

                if (!string.IsNullOrEmpty(this.Request["property"])) {
                    qsOther = "&property=" + this.Request["property"] 
                        + "&belongsTo=" + this.Request["belongsTo"];
                }

                this.Response.Redirect("/Edit.aspx?EntityId=" + this.Request["EntityId"] + "&ObjectId=" + qsOther);
            }
        }

        protected void ButtonSaveClose_Click(object sender, EventArgs e)
        {
            if (this.Save().State == ActionState.Succesful)
            {
                this.LiteralJS.Text = @"
<script type=""text/javascript"">
    $(document).ready(function () {
        ModalReturn(-1);
    });
</script>
";
            }
        }

        protected void LoadEntity(Entity entity, EntityEdit entityEdit, object obj)
        {
            // load the entity
            Apollo.Core.Form form = null;

            if (entityEdit == null)
                form = Apollo.Core.Form.GenerateDefault(entity);
            else
                form = Apollo.Core.Form.GenerateFromString(entityEdit.EditString, entity);

            // check for user rights
            if (!Apollo.Application.Current.IsAllowed(Apollo.Application.ACTION_SAVE_NAME, entity, obj))
            {
                ActionSave.Visible = false;
                ActionSaveNew.Visible = false;
                ActionSaveClose.Visible = false;
                ActionDelete.Visible = false;

                if (Apollo.Application.Current.IsAllowed(Apollo.Application.ACTION_VIEW_NAME, entity, obj))
                    form.SetFormType(FormItemType.ReadOnly);
                else
                {
                    MessageList.AddMessage(new ActionMessage("No access rights!", ActionMessageType.Security));
                    return;
                }
            }
            else if (!Apollo.Application.Current.IsAllowed(Apollo.Application.ACTION_DELETE_NAME, entity, obj))
            {
                ActionDelete.Visible = false;
            }

            // Load the form
            LoadForm(form, obj);

            // Load tabs and actions
            if (obj != null)
            {
                if (form.AppendTabs)
                {
                    try
                    {
                        LoadTabs(entity, obj);
                    }
                    catch (Exception ex)
                    {
                        MessageList.AddMessage(new ActionMessage(ex.Message, ActionMessageType.Error));
                    }
                }

                LoadActions(entity, obj);
            }
            else
            {
                ActionDelete.Visible = false;
            }

            string dateFormat = "dd/mm/yy";
            if (!string.IsNullOrEmpty(Apollo.Application.CurrentLanguage.DateTimeFormat))
                dateFormat = Apollo.Application.CurrentLanguage.DateTimeFormat.ToLower().Replace("yyyy", "yy");



            LiteralJS.Text += @"
    <script type=""text/javascript"">
        $(document).ready(function() {
            $( "".property_datetime"" ).datepicker({ dateFormat: """ + dateFormat + @""", changeMonth: true, changeYear: true, showWeek: true, firstDay: 1 });
        });

        /**** Variables ****/
        " + jsLoadTabVariablesDefinition + @" 

        function loadTab(tabIndex) {
            /**** Method for loading ****/
            " + jsLoadTabMethodDefinition + @" 
        }
    </script>";


            // check if it is a post back from an action
            if (this.Request.Form["__EVENTTARGET"] == "APOLLO_ENTITY_ACTION")
                this.DoAction(this.Request.Form["__EVENTARGUMENT"], obj);
        }

        protected void LoadActions(Entity entity, object obj)
        {
            string actionsJavaScript = "";

            foreach (EntityAction action in Apollo.Application.Meta<EntityAction>().Where<EntityAction>(ea => ea.Entity.Id == entity.Id && (ea.Applicability == EntityActionApplicability.Edit || ea.Applicability == EntityActionApplicability.Global)))
            {
                if (Apollo.Application.Current.IsAllowed(action.Code, entity, obj))
                {
                    actionsJavaScript += @"
    // add action " + action.Name + @"
    $(""#entity_menu"").append(CreateMenuItem("" " + action.Name + @""",""" + action.IconPath + @""", null, function() {
        __doPostBack('APOLLO_ENTITY_ACTION','" + action.Code + @"');
    }));
            ";
                }
            }

            if (!string.IsNullOrEmpty(actionsJavaScript))
                LiteralJS.Text += @"
    <script type=""text/javascript"">
        $(document).ready(function() {
            " + actionsJavaScript + @"
        });
    </script>";
        }

        protected void LoadForm(Form form, object obj)
        {
            if (form == null)
                return;

            foreach (FormGroup group in form.Groups)
            {
                Controls_Edit_FormGroup grControl = this.LoadControl("~/Controls/Edit/FormGroup.ascx") as Controls_Edit_FormGroup;

                if (grControl == null) {
                    MessageList.AddMessage(new ActionMessage("Failed to load 'FormGroup.ascx' control.", ActionMessageType.System));
                    continue;
                }

                grControl.Group = group;
                grControl.TargetObject = obj;

                this.formGroups.Add(grControl);

                if (group.Type == FormGroupType.Properties)
                    PlaceHolderEditForm.Controls.Add(grControl);
                else if (group.Type == FormGroupType.ReverseProperty)
                {

                    string filter =
                        "{\"groupOp\":\"AND\",\"groups\":[],\"rules\":[{"
                        + "\"field\":\"" + group.ReverseProperty.PropertyName + "." + group.ReverseProperty.Entity.IdentifierProperty + "\","
                        + "\"op\":\"eq\","
                        + "\"data\":\"" + Request["ObjectId"] + "\""
                        + "}]}";

                    LanguageDefinition ld = Apollo.Application.Meta<LanguageDefinition>()
                        .SingleOrDefault<LanguageDefinition>(l => l.Type == "ReverseProperty"
                            && l.CodeHi == group.ReverseProperty.Entity.Name
                            && l.CodeLo == group.ReverseProperty.PropertyName
                            && l.Language.Id == Apollo.Application.CurrentLanguage.Id);

                    if (group.View == null && group.ReverseProperty.ReverseRelationItemView == null)
                        MessageList.AddMessage(new ActionMessage("No view defined between for group \"" + group.DisplayName + "\"", ActionMessageType.System));
                    else
                        this.AddTab(ld == null ? group.ReverseProperty.Entity.Name + " - " + group.ReverseProperty.PropertyName : ld.DisplayName
                            , group.ReverseProperty
                            , group.View != null ? group.View : group.ReverseProperty.ReverseRelationItemView
                            , filter);
                }
                else
                    this.AddTab(group.DisplayName, grControl);
            }
        }

        protected void LoadTabs(Entity entity, object obj)
        {
            foreach (EntityProperty property in Apollo.Application.Meta<EntityProperty>()
                        .Where<EntityProperty>(ep => ep.BelongToEntity != null && ep.BelongToEntity.Id == entity.Id
                            && ep.ReverseRelationItemView != null))
            {

                // ignore null views
                if (property.Entity.DefaultView == null)
                    continue;

                string filter =
                    "{ \"groupOp\": \"AND\",\"groups\" : [],\"rules\" : [ {"
                    + "\"field\": \"" + property.PropertyName + "." + entity.IdentifierProperty + "\", "
                    + "\"op\": \"eq\", "
                    + "\"data\": \"" + Request["ObjectId"] + "\""
                    + "} ] }";

                LanguageDefinition ld = Apollo.Application.Meta<LanguageDefinition>()
                    .SingleOrDefault<LanguageDefinition>(l => l.Type == "ReverseProperty"
                        && l.CodeHi == property.Entity.Name
                        && l.CodeLo == property.PropertyName
                        && l.Language.Id == Apollo.Application.CurrentLanguage.Id);

                this.AddTab(ld == null ? property.Entity.Name + " - " + property.PropertyName : ld.DisplayName
                    , property
                    , property.ReverseRelationItemView
                    , filter);
            }

            string dateFormat = "dd/mm/yy";
            if (!string.IsNullOrEmpty(Apollo.Application.CurrentLanguage.DateTimeFormat))
                dateFormat = Apollo.Application.CurrentLanguage.DateTimeFormat.ToLower().Replace("yyyy", "yy");


        }

        #region Get object from the db

        public object Get(Entity entity, object objectId)
        {
            try
            {
                if (!string.IsNullOrEmpty(entity.CurrentFilter))
                {
                    FilterGroup group = WebHelper.DeserializeGroupFromJSON(
                        "{groupOp:\"AND\",groups:[" + entity.CurrentFilter + "],rules:[{field:\"" + entity.IdentifierProperty + "\",op:\"eq\",data:\"" + Apollo.Application.Serialize(objectId) + "\"}]}"
                        , entity.MappedClass);

                    ICriteria criteria = Apollo.Application.CurrentSession.CreateCriteria(entity.MappedClass);

                    foreach (Projection projection in group.Projections)
                        this.MapProjectionToAlias(projection);

                    foreach (KeyValuePair<string, string> alias in aliases)
                    {
                        criteria = criteria
                            .CreateAlias(alias.Value, alias.Key, NHibernate.SqlCommand.JoinType.LeftOuterJoin)
                            //.SetLockMode(alias.Key, LockMode.None)
                            .SetFetchMode(alias.Key, FetchMode.Lazy);
                    }

                    criteria.Add(group.ToCriterion());

                    object returnObject = criteria.UniqueResult();

                    if (returnObject == null)
                    {
                        returnObject = Apollo.Application.CurrentSession.Get(entity.MappedClass, objectId);

                        if (returnObject != null)
                            MessageList.AddMessage(new ActionMessage("No rights to view this", ActionMessageType.Security));
                    }

                    return criteria.UniqueResult();
                }
                else
                    return Apollo.Application.CurrentSession.Get(entity.MappedClass, objectId);
            }
            catch
            {
                MessageList.AddMessage(new ActionMessage("Failed to load", ActionMessageType.Error));

                return null;
            }
        }

        protected static string PATH_SEPARATOR = "____";
        protected Dictionary<string, string> aliases = new Dictionary<string, string>();
        protected void MapProjectionToAlias(Projection from)
        {
            // get actual path
            string path = from.ProjectionPathWithoutProperty;

            // if it is a base path
            if (string.IsNullOrEmpty(path))
            {
                from.Alias = "this";
                return;
            }

            from.Alias = path.Replace(".", PATH_SEPARATOR);

            if (!aliases.ContainsKey(from.Alias))
            {
                // add it to 
                string[] parts = path.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                if (parts.Length == 0)
                {
                    from.Alias = "this";
                    return;
                }

                string alias = parts[0];
                if (!aliases.ContainsKey(alias))
                    aliases.Add(alias, alias);

                for (int index = 1; index < parts.Length; index++)
                {
                    string alias_property = alias + "." + parts[index];
                    alias += PATH_SEPARATOR + parts[index];

                    if (!aliases.ContainsKey(alias))
                        aliases.Add(alias, alias_property);
                }
            }
        }

        #endregion


        protected int tabIndex = 1;
        protected string jsLoadTabVariablesDefinition = "";
        protected string jsLoadTabMethodDefinition = "";
        protected void AddTab(string tabName, EntityProperty property, EntityView view, string baseFilter)
        {
            // increment tab index
            tabIndex++;
            
            // tab icon
            string imageHtml = string.Format("<img src=\"{0}\" align=\"left\" border=\"0\" />&nbsp;",
                string.IsNullOrEmpty(view.Entity.IconPath) ? "/css/icons/icon_01.png" : view.Entity.IconPath);

            if (view.Entity.Loading != EntityLoading.HeavyLoading)
            {
                // row count
                ProjectionManager manager = new ProjectionManager(view.ViewString, view.Entity.MappedClass);
                manager.AddFilter(baseFilter);

                int rowCount = manager.RowCount();

                LiteralTabsHeaders.Text += string.Format("<li><a href=\"#tabs-{0}\">{3} {1} ({2})</a></li>", new object[] { tabIndex, tabName, rowCount, imageHtml });
            }
            else{
                LiteralTabsHeaders.Text += string.Format("<li><a href=\"#tabs-{0}\">{2} {1} (NA)</a></li>", new object[] { tabIndex, tabName, imageHtml });
            }

            // format it to be used with javascript
            if(!string.IsNullOrEmpty(baseFilter))
                baseFilter = baseFilter.Replace("\\", "\\\"").Replace("\"", "\\\"");


            Controls_TabBodyEntityGridView tegv = this.LoadControl("~/Controls/TabBodyEntityGridView.ascx") as Controls_TabBodyEntityGridView;
            PlaceHolderTabsBody.Controls.Add(tegv);
            tegv.TabIndex = tabIndex;
            tegv.EntityView = view;
            tegv.FilterString = baseFilter;
            tegv.EditUrl = (string.IsNullOrEmpty(view.Entity.CustomEditUrl) ?
                "Edit.aspx?EntityId=" + view.Entity.Id.ToString() + "&ObjectId={ObjectId}" :
                view.Entity.CustomEditUrl)

                + "&property=" + property.PropertyName + "&belongsTo=" + this.Request["ObjectId"];

            // js to load the data only on tab loading
            string tabIndexForJS = (tabIndex - 1).ToString();
            jsLoadTabVariablesDefinition += "\nvar isLoadedTab" + tabIndexForJS + " = false;";
            jsLoadTabMethodDefinition += @"
            if(tabIndex == " + tabIndexForJS + @" && !isLoadedTab" + tabIndexForJS + @") {
                " + tegv.GetJavaScriptMethodDefinition() + @"
                isLoadedTab" + tabIndexForJS + @" = true;
            }

        ";
        }
        protected void AddTab(string tabName, Control innerControl)
        {
            // increment tab index
            tabIndex++;
            LiteralTabsHeaders.Text += string.Format("<li><a href=\"#tabs-{0}\">{1}</a></li>", new object[] { tabIndex, tabName });

            PlaceHolderTabsBody.Controls.Add(new LiteralControl("<div id='tabs-" + tabIndex.ToString() + "'>"));
            PlaceHolderTabsBody.Controls.Add(innerControl);
            PlaceHolderTabsBody.Controls.Add(new LiteralControl("</div>"));
        }


    }
}